<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        user-select: none;
      }

      #box {
        position: relative;
        width: 300px;
        height: auto;
        margin: 0 auto;
        padding: 100px;
        border: 1px solid;
      }

      li {
        display: inline-block;
        margin: 5px;
      }

      input {
        width: 30px;
        height: 30px;
      }

      #moveSelected {
        position: fixed;
        top: 0;
        left: 0;
        border: 1px dashed #2783f5;
        pointer-events: none;
      }
    </style>
  </head>
  <body>
    <ul id="box">
      <li><input type="checkbox" id="check1" /></li>
      <li><input type="checkbox" id="check2" /></li>
      <li><input type="checkbox" id="check3" /></li>
      <li><input type="checkbox" id="check4" /></li>
      <li><input type="checkbox" id="check5" /></li>
      <li><input type="checkbox" id="check6" /></li>
      <li><input type="checkbox" id="check7" /></li>
      <li><input type="checkbox" id="check8" /></li>
      <li><input type="checkbox" id="check9" /></li>
      <li><input type="checkbox" id="check10" /></li>
      <li><input type="checkbox" id="check11" /></li>
      <li><input type="checkbox" id="check12" /></li>
      <li><input type="checkbox" id="check13" /></li>
      <li><input type="checkbox" id="check14" /></li>
      <li><input type="checkbox" id="check15" /></li>
      <li><input type="checkbox" id="check16" /></li>
      <li><input type="checkbox" id="check17" /></li>
      <li><input type="checkbox" id="check18" /></li>
      <li><input type="checkbox" id="check19" /></li>
      <li><input type="checkbox" id="check20" /></li>
      <!-- 选区 -->
      <li>
        <div id="moveSelected"></div>
      </li>
    </ul>
    <script>
      window.onload = function () {
        let flag = false; //是否开启拖拽
        let oldLeft = 0; //鼠标按下时的位置
        let oldTop = 0;
        let box = document.getElementById("box"); //操作区
        let moveSelected = document.getElementById("moveSelected"); //选区
        let checkboxs = box.getElementsByTagName("input"); //复选框
        // 鼠标按下时开启拖拽，给选区设置定位
        box.onmousedown = function (e) {
          flag = true;
          moveSelected.style.top = e.pageY + "px";
          moveSelected.style.left = e.pageX + "px";
          oldLeft = e.pageX;
          oldTop = e.pageY;
        };
        // 鼠标移动时计算选区的位置和大小
        box.onmousemove = function (e) {
          if (!flag) return;

          if (e.pageX < oldLeft) {
            //表示左移
            moveSelected.style.left = e.pageX + "px";
            moveSelected.style.width = oldLeft - e.pageX + "px"; //向左移动的距离作为选区的宽
          } else {
            moveSelected.style.width = event.pageX - oldLeft + "px";
          }
          if (e.pageY < oldTop) {
            //向上移动
            moveSelected.style.top = e.pageY + "px";
            moveSelected.style.height = oldTop - e.pageY + "px";
          } else {
            moveSelected.style.height = e.pageY - oldTop + "px";
          }
          //通过得到的left和top加上元素自身的宽高来计算选区的right和bottom
          moveSelected.style.bottom =
            Number(moveSelected.style.top.split("px")[0]) +
            Number(moveSelected.style.height.split("px")[0]) +
            "px";
          moveSelected.style.right =
            Number(moveSelected.style.left.split("px")[0]) +
            Number(moveSelected.style.width.split("px")[0]) +
            "px";
          //找出选中的区域并激活
          for (let i = 0; i < checkboxs.length; i++) {
            //计算每个checkbox的位置信息
            let left = checkboxs[i].offsetLeft + box.offsetLeft;
            let right = checkboxs[i].offsetWidth + left;
            let top = checkboxs[i].offsetTop + box.offsetTop;
            let bottom = checkboxs[i].offsetHeight + top;
            //判断是否在选择区
            let leftCover =
              moveSelected.style.left.split("px")[0] <= left &&
              left <= moveSelected.style.right.split("px")[0];
            let rightCover =
              moveSelected.style.left.split("px")[0] <= right &&
              right <= moveSelected.style.right.split("px")[0];
            let topCover =
              moveSelected.style.top.split("px")[0] <= top &&
              top <= moveSelected.style.bottom.split("px")[0];
            let bottomCover =
              moveSelected.style.top.split("px")[0] <= bottom &&
              bottom <= moveSelected.style.bottom.split("px")[0];
            if ((leftCover || rightCover) && (topCover || bottomCover)) {
              checkboxs[i].checked = true; //激活复选框
            }
          }
        };
        //鼠标抬起时清空选区数据
        box.onmouseup = function (e) {
          if (!flag) return;
          flag = false;
          moveSelected.style.width = 0;
          moveSelected.style.height = 0;
          moveSelected.style.top = 0;
          moveSelected.style.left = 0;
          moveSelected.style.bottom = 0;
          moveSelected.style.right = 0;
        };
        // 鼠标超出ul选区失效
        box.onmouseleave = function (e) {
          flag = false;
          moveSelected.style.width = 0;
          moveSelected.style.height = 0;
          moveSelected.style.top = 0;
          moveSelected.style.left = 0;
        };
      };
    </script>
  </body>
</html>

<!-- 
React 实现的框选，并且实现逻辑也不同

eg：
import { CSSProperties, FC, useEffect, useMemo, useRef, useState } from "react";
import { css } from "@emotion/css";

interface Area {
  s: { x: number; y: number };
  e: { x: number; y: number };
}

const wrapCls = css`
  width: 800px;
  height: 800px;
  margin: auto;
  border: 1px solid;
`;

const boxCls = css`
  position: fixed;
  top: 0;
  left: 0;
  background-color: red;
  width: 0;
  height: 0;
  z-index: 2;
`;

const SelectBox: FC = () => {
  const wrap = useRef<HTMLDivElement | null>(null);
  const [area, setArea] = useState<Area | null>(null);
  useEffect(() => {
    const controller = new AbortController();
    const handlerMouseDown = (e: MouseEvent) => {
      if (e.button !== 0) return;
      const main = wrap.current!;
      const rect = main.getBoundingClientRect();
      const clientX = e.clientX;
      const clientY = e.clientY;
      if (
        clientX >= rect.left &&
        clientX <= rect.left + rect.width &&
        clientY >= rect.top &&
        clientY <= rect.top + rect.height
      ) {
        const controller = new AbortController();
        const handlerMouseMove = (e: MouseEvent) => {
          const pos = {
            x: Math.max(rect.left, Math.min(rect.left + rect.width, e.clientX)),
            y: Math.max(rect.top, Math.min(rect.top + rect.height, e.clientY)),
          };
          setArea({
            s: { x: clientX, y: clientY },
            e: pos,
          });
        };
        const handlerMouseUp = () => {
          setArea(null);
          controller.abort();
        };
        window.addEventListener("mousemove", handlerMouseMove, {
          signal: controller.signal,
        });
        window.addEventListener("mouseup", handlerMouseUp, {
          signal: controller.signal,
        });
      }
    };

    window.addEventListener("mousedown", handlerMouseDown, {
      signal: controller.signal,
    });

    return () => {
      controller.abort();
    };
  }, []);

  const style: CSSProperties = useMemo(() => {
    if (area) {
      return {
        left: Math.min(area.s.x, area.e.x),
        top: Math.min(area.s.y, area.e.y),
        width: Math.abs(area.e.x - area.s.x),
        height: Math.abs(area.e.y - area.s.y),
      };
    }
    return {};
  }, [area]);

  return (
    <div>
      <div className={wrapCls} ref={wrap}>
        {area && <div className={boxCls} style={style}></div>}
      </div>
    </div>
  );
};

export default SelectBox;


-->
